using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

using Microsoft.WowAddonStudio.Lua.AST;
using Microsoft.WowAddonStudio.Lua.Parser;
using Microsoft.WowAddonStudio.Lua.Support;

namespace Microsoft.WowAddonStudio.Lua.Model
{
    /// <summary>
    /// A scope that can be associated with a Lua <see cref="Node"/>.
    /// </summary>
    [DebuggerDisplay("Level = {Level}")]
    public class Scope
    {
        private Scope parentScope;
        private readonly List<Scope> childScopes = new List<Scope>();

        /// <summary>
        /// Initializes a new instance of the <see cref="Scope"/> class.
        /// </summary>
        /// <param name="parentScope">The parent scope.</param>
        public Scope(Scope parentScope)
        {
            this.Parent = parentScope;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Scope"/> class.
        /// </summary>
        public Scope()
        {
        }

        /// <summary>
        /// Gets or sets the name of the scope. Can be null.
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// Gets or sets the location of the scope.
        /// </summary>
        public LexLocation Location { get; set; }

        /// <summary>
        /// Gets the parent scope or returns <c>null</c> if it is the global scope.
        /// </summary>
        public Scope Parent
        {
            get { return parentScope; }
            set
            {
                if (parentScope != value)
                {
                    // Remove scope from current parent scope
                    if (parentScope != null)
                        this.parentScope.childScopes.Remove(this);

                    // Update parent scope
                    this.parentScope = value;

                    // Add scope to new parent scope
                    if (parentScope != null)
                        this.parentScope.childScopes.Add(this);
                }
            }
        }

        /// <summary>
        /// Gets the child scopes of this scope.
        /// </summary>
        public IEnumerable<Scope> ChildScopes
        {
            get { return childScopes; }
        }

        /// <summary>
        /// Returns the global scope that contains this scope.
        /// </summary>
        public Scope GlobalScope
        {
            get
            {
                return this.Parent != null ? this.Parent.GlobalScope : this;
            }
        }

        /// <summary>
        /// Gets the level of scoping for the current scope.
        /// </summary>
        public int Level
        {
            get { return this.Parent != null ? this.Parent.Level + 1 : 1; }
        }

        /// <summary>
        /// Gets a value indicating whether this scope is a global scope.
        /// </summary>
        public bool IsGlobalScope
        {
            get { return this.Level == 1; }
        }

        /// <summary>
        /// Finds a scope by its name.
        /// </summary>
        /// <param name="scopeName">The name of the scope.</param>
        /// <returns>An instance of the <see cref="Scope"/> class if found; null otherwise.</returns>
        public Scope FindScope(string scopeName)
        {
            if (scopeName == null)
                throw new ArgumentNullException("scopeName");

            foreach (var childScope in childScopes)
            {
                if (childScope.Name == scopeName)
                    return childScope;
            }

            return null;
        }

        /// <summary>
        /// Finds the scope that contains the given position in a named scope.
        /// </summary>
        /// <param name="scopeName">The name of the scope.</param>
        /// <param name="line">The line number.</param>
        /// <param name="column">The column index.</param>
        /// <returns>An instance of the <see cref="Scope"/> class.</returns>
        public Scope FindScope(string scopeName, int line, int column)
        {
            if (scopeName == null)
                throw new ArgumentNullException("scopeName");

            var scope = this.FindScope(scopeName);
            if (scope != null)
                return scope.FindScope(line, column);

            return null;
        }

        /// <summary>
        /// Finds the scope that contains the given position.
        /// </summary>
        /// <param name="line">The line number.</param>
        /// <param name="column">The column index.</param>
        /// <returns>An instance of the <see cref="Scope"/> class.</returns>
        public Scope FindScope(int line, int column)
        {
            foreach (var childScope in childScopes)
            {
                if (childScope.Location.Contains(line, column))
                    return childScope.FindScope(line, column);
            }

            return this;
        }

        /// <summary>
        /// Merges a scope and its child scopes into the scope.
        /// </summary>
        /// <param name="scope">The scope to merge.</param>
        public virtual void Merge(Scope scope)
        {
            if (scope == null)
                throw new ArgumentNullException("scope");

            // Iterate through the child scopes of the other scope and update their parent
            foreach (var childScope in scope.ChildScopes.ToArray())
                childScope.Parent = this;
        }

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </returns>
        public override string ToString()
        {
            return String.Format("{0} (Level = {1})", this.Name ?? "Unnamed", this.Level);
        }
    }
}